1 /*
2 Copyright: Marcelo S. N. Mancini (Hipreme|MrcSnm), 2018 - 2025
3 License:   [https://creativecommons.org/licenses/by/4.0/|CC BY-4.0 License].
4 Authors: Marcelo S. N. Mancini
5 
6 	Copyright Marcelo S. N. Mancini 2018 - 2025.
7 Distributed under the CC BY-4.0 License.
8    (See accompanying file LICENSE.txt or copy at
9 	https://creativecommons.org/licenses/by/4.0/
10 */
11 module hip.assets.image;
12 public import hip.api.data.asset;
13 public import hip.api.data.image;
14 
15 
16 /**
17 *   This class represents pixel data on RAM (CPU Powered)
18 *   this is useful for loading images on another thread and then
19 *   sending it to the GPU
20 */
21 public class Image : HipAsset, IImage
22 {
23     IHipImageDecoder decoder;
24     string imagePath;
25     int width, height;
26     ubyte bytesPerPixel;
27     ushort bitsPerPixel;
28     
29     ubyte[] pixels;
30 
31     this(uint width, uint height, ubyte bytesPerPixel)
32     {
33         super("Image");
34         pixels = new ubyte[width*height*bytesPerPixel];
35         this.bytesPerPixel = bytesPerPixel;
36         this.height = height;
37         this.width = width;
38     }
39 
40     this(string path = "")
41     {
42         import hip.util.system : sanitizePath;
43         path = sanitizePath(path);
44         super("Image_"~path);
45         imagePath = path;
46     }
47     ///Loads an arbitrary buffer which will be decoded by the decoder.
48     this(in string path, in ubyte[] buffer, void delegate(IImage self) onSuccess, void delegate() onFailure)
49     {
50         this(path);
51         loadFromMemory(cast(ubyte[])buffer, onSuccess, onFailure);
52     }
53 
54     static immutable(IImage) getPixelImage()
55     {
56         __gshared Image img;
57         __gshared ubyte[4] pixel = IHipImageDecoder.getPixel();
58         if(img is null)
59         {
60             img = new Image("Pixel");
61             img.pixels = pixel;
62             img.width = 1;
63             img.height = 1;
64             img.bytesPerPixel = 4;
65         }
66         return cast(immutable)img;
67     }
68     string getName() const {return imagePath;}
69     uint getWidth() const {return width;}
70     uint getHeight() const {return height;}
71     ubyte getBytesPerPixel() const {return bytesPerPixel;}
72     const(ubyte[]) getPalette() const {return decoder ? decoder.getPalette : null;}
73     const(ubyte[]) getPixels() const {return pixels;}
74 
75     void loadRaw(in ubyte[] pixels, int width, int height, ubyte bytesPerPixel)
76     {
77         this.width = width;
78         this.height = height;
79         this.pixels = cast(ubyte[])pixels;
80         this.bytesPerPixel = bytesPerPixel;
81         this.bitsPerPixel = cast(ubyte)(bytesPerPixel*8);
82     }
83 
84 
85     bool loadFromMemory(ubyte[] data, void delegate(IImage self) onSuccess, void delegate() onFailure)
86     {
87         if(data.length == 0)
88             throw new Exception("No data was passed to load Image. Could not load image at path "~imagePath);
89         if(decoder is null)
90             decoder = getDecoder(imagePath);
91 
92         if(!(decoder.startDecoding(data, ()
93         {
94             width         = decoder.getWidth();
95             height        = decoder.getHeight();
96             bitsPerPixel  = decoder.getBitsPerPixel();
97             bytesPerPixel = decoder.getBytesPerPixel();
98             pixels        = cast(ubyte[])decoder.getPixels();
99             onSuccess(this);
100         }, onFailure)))
101             throw new Exception("Decoding Image Error: Could not load image "~imagePath);
102         return true;
103     }
104 
105     bool hasLoadedData() const {return pixels !is null && width != 0 && height != 0;}
106 
107     override bool isReady() const { return hasLoadedData(); }
108 
109     ubyte[] monochromeToRGBA() const
110     {
111         ubyte[] pix = new ubyte[](4*width*height); //RGBA for each pixel
112         uint pixelsLength = width*height;
113         ubyte color;
114         uint z;
115         for(uint i = 0; i < pixelsLength; i++)
116         {
117             //Palette r color = palette[pixels[i]*4]
118             color = (cast(ubyte*)pixels)[i];
119             pix[z++] = color; //R
120             pix[z++] = color; //G
121             pix[z++] = color; //B
122             pix[z++] = color; //A
123         }
124 
125         return pix;
126     }
127 
128     ubyte[] convertPalettizedToRGBA() const
129     {
130         ubyte[] pix = new ubyte[](4*width*height); //RGBA for each pixel
131         uint pixelsLength = width*height;
132         const(ubyte[]) palette = decoder.getPalette();
133 
134         uint colorIndex;
135         uint z;
136         for(uint i = 0; i < pixelsLength; i++)
137         {
138             //Palette r color = palette[pixels[i]*4]
139             colorIndex = (cast(ubyte*)pixels)[i]*4;
140             pix[z++]   = palette[colorIndex]; //R
141             pix[z++] = palette[colorIndex+1]; //G
142             pix[z++] = palette[colorIndex+2]; //B
143             pix[z++] = palette[colorIndex+3]; //A
144         }
145 
146         return pix;
147     }
148 
149     override void onDispose() {decoder.dispose(); }
150     override void onFinishLoading() { }
151     alias w = width;
152     alias h = height;
153 }
154 
155